package org.fjsei.yewu.resolver.sei;

import com.alibaba.fastjson2.JSON;
import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;
import lombok.extern.slf4j.Slf4j;
import md.cm.base.Company;
import md.cm.base.Companies;
import md.cm.base.Person;
import md.cm.base.Persons;
import md.cm.flow.ApprovalStmRepository;
import md.cm.geography.*;
import md.cm.unit.*;
import md.specialEqp.*;
import md.specialEqp.inspect.*;
import md.specialEqp.type.*;
import md.system.AuthorityRepository;
import md.system.User;
import md.system.UserRepository;
import org.fjsei.yewu.entity.fjtj.HrUserinfoRepository;
import org.fjsei.yewu.exception.CommonGraphQLException;
import org.fjsei.yewu.index.*;
import org.fjsei.yewu.input.DeviceCommonInput;
import org.fjsei.yewu.input.PipingUnitInput;
import org.fjsei.yewu.input.UnitCommonInput;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.security.*;
import org.fjsei.yewu.bpm.ApprovalStmService;
import org.fjsei.yewu.util.Tool;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.persistence.CacheRetrieveMode;
import jakarta.persistence.CacheStoreMode;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.*;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_RETRIEVE_MODE;
import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_STORE_MODE;


//实际相当于controller;一般都有MutationResolver反而QueryResolver不一定需要，@Autowired最好能够同类功能汇集，避免每个模块都上很多xxxRepository注入。
//这个类名字不能重复简明！
//GraphQL有非常重要的一个特点：强类型,自带graphQL基本类型标量Int, Float, String, Boolean和ID。　https://segmentfault.com/a/1190000011263214
//乐观锁确保任何更新或删除不会被无意地覆盖或丢失。悲观锁会在数据库级别上锁实体会引起DB级死锁。 https://blog.csdn.net/neweastsun/article/details/82318734
@Slf4j
@Controller
public class BaseMutation extends Comngrql  {
    //private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;     //可改成 UserDetailsService类型 就行的！
    @Autowired
    private Equipments eQPRepository;
    @Autowired
    private EqpEsRepository eqpEsRepository;
    @Autowired
    private IspRepository iSPRepository;
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private TaskRepository taskRepository;
    @Autowired
    private ReportRepository reportRepository;
    @Autowired
    private Units units;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired
    private TownRepository townRepository;
    @Autowired
    private AdminunitRepository adminunitRepository;
    @Autowired
    private HrUserinfoRepository hrUserinfoRepository;
    @Autowired
    private Companies companies;
    @Autowired   private CompanyEsRepository companyEsRepository;
    @Autowired
    private Persons persons;
    @Autowired   private PersonEsRepository personEsRepository;
    @Autowired  private ApprovalStmRepository approvalStmRepository;
    @Autowired  private ApprovalStmService approvalStmService;
    @Autowired  private PipingUnitRepository pipingUnitRepository;
    @Autowired private DivisionRepository divisionRepository;
    @Autowired private VillageRepository villageRepository;

    @PersistenceContext(unitName = "entityManagerFactorySei")
    private EntityManager emSei;                //EntityManager相当于hibernate.Session：
    //@Autowired private GenericApplicationContext context;

//    @Autowired
//    private final JwtTokenUtil jwtTokenUtil=new JwtTokenUtil();

    @Value("${sei.cookie.domain:}")
    private final String  cookieDomain="";
    @Value("${server.ssl.enabled:false}")
    private boolean  isSSLenabled;

    //@Autowired
    private PasswordEncoder passwordEncoder;
    private final JwtTokenProvider jwtTokenProvider;
    @Value("${jwt.access-token-validity-in-seconds}")
    private final int  accessTokenValidityInSeconds;

    public BaseMutation(PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider) {
        this.passwordEncoder = passwordEncoder;
        this.jwtTokenProvider = jwtTokenProvider;
        accessTokenValidityInSeconds = 5400;
    }
    /**新建立设备
     * type='cod' 'oid'指示按照代码从旧平台导入设备数据。
    * */
    @Transactional(rollbackFor = Exception.class)
    public Equipment newEQP(String type, DeviceCommonInput inp) {
        Eqp eqpold= newEQP导入(type,inp);
        if(null!=eqpold)    return eqpold;
        Eqp.EqpBuilder<?, ?>  eqpBld=null;
        if(type.equals("3"))
            eqpBld = Elevator.builder().flo(inp.getFlo());
        else if(type.equals("2") || type.equals("R"))
            eqpBld = Vessel.builder().pnum(inp.getFlo());
        else if(type.equals("1"))
            eqpBld = Boiler.builder();
        else if(type.equals("4"))
            eqpBld = Crane.builder();
        else if(type.equals("5"))
            eqpBld = FactoryVehicle.builder();
        else if(type.equals("6"))
            eqpBld = Amusement.builder();
        else if(type.equals("8"))
            eqpBld = Pipeline.builder();
        else if(type.equals("9"))
            eqpBld = Ropeway.builder();
        else
            eqpBld = Eqp.builder();

        Eqp eQP =eqpBld.cod(inp.getCod()).type(type).oid(inp.getOid()).reg(RegState_Enum.values()[3])
                .ust(UseState_Enum.USE).sort(inp.getSort()).vart(inp.getVart()).subv(inp.getSubv())
                .build();

        //这样无法执行Set<Task> task=new HashSet<>();原来new Eqp()却可以的。
        //【重大变更】 从Eqp不能直接获得task，要改成Eqp.isps.task来间接获得任务信息。
        /*Task task=new Task();
        task.setDep("12111kk234fv");
        Set<Task> tasks=new HashSet<>();
        tasks.add(task);
        eQP.setTask(tasks);     //getTask().add(task);
        List<Eqp> devs=new ArrayList<>();
        devs.add(eQP);
        //多对多保存复杂一点，必须都给set上。
        //task.setDevs(devs);
        Task task2=new Task();
        task2.setDep("aAxwxxxx3f4fv");
        eQP.getTask().add(task2);
        List<Eqp> devs2=new ArrayList<>();
        devs2.add(eQP);
        //task2.setDevs(devs2);
        //task.setStatus("STOPted");
        //task2.setStatus("RuningYe");
        */

        try {
          //  taskRepository.save(task);
           // taskRepository.save(task2);
            eQPRepository.saveAndFlush(eQP);
            //这里保存若事务异常就导致下面ES更新无法回滚了。
            //Eqp sec = eQP instanceof Elevator ? ((Elevator) eQP) : null;
            //if( !(sec instanceof Elevator) )       return eQP;
            //TODO: flush tasks ... 尽量确保JPA不出现异常否则ES就不一致了。
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return null;
        }
       // EqpEs eqpEs=new EqpEs();
        //嵌套对象会直接引用ref的=浅层拷贝。嵌套的关联对象给Elasticsearch也是全量都存储的。
      //直接用BeanUtils.copyProperties(eQP,eqpEs); 　 若有字段嵌套对象类型不一样的就会报错。
        String json = JSON.toJSONString(eQP);       //支持自循环嵌套不报错
        EqpEs eqpEs=JSON.parseObject(json, EqpEs.class);   //遇到某字段的属于嵌套对象类型并且该字段对象类型还变化的情况也可顺利转换。
    //    eqpEs.getTask().forEach(item -> {
   //     });
        //ES不支持事务与回滚。
        //相互关联导致的ES存储死循环：EqpEs->task->Eqp->task,这样task id自循环导致。表现为newEQP函数上事务上死锁。
        eqpEsRepository.save(eqpEs);
        //这个时间保存ES若异常可自动取消eQPRepository.saveAndFlush的操作结果。
        //运行到这还处于Transactional范围下，可是Elasticsearch已经发给服务器去处理了，JPA却还没有交给数据库去存储。
        return (Equipment) eQP;
    }

    @Transactional
    public PipingUnit newPipingUnit(String pipeId,String code,PipingUnitInput inp) {
        Tool.ResolvedGuuid gId= Tool.fromGluuId(pipeId);
        Eqp eqp = eQPRepository.findById(gId.getId()).orElse(null);
        Assert.isTrue(eqp != null,"未找到Eqp:"+pipeId);
        Assert.isTrue(eqp instanceof Pipeline,"非管道装置:"+pipeId);
        PipingUnit pipingUnit=PipingUnit.builder().code(code).pipe( (Pipeline)eqp ).ust(UseState_Enum.USENOTREG)
                .build();
        pipingUnitRepository.save(pipingUnit);
        return pipingUnit;
    }

   /* @Transactional
    public Adminunit initAdminunit(Long townId, String prefix, String areacode, String zipcode) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Town town = townRepository.findById(townId).orElse(null);
        Assert.isTrue(town != null,"未找到town:"+townId);
        Adminunit adminunit=adminunitRepository.findByTownIs(town);
        if(adminunit==null){
            adminunit=new Adminunit();
            adminunit.setTown(town);
            adminunit.setCounty(town.getCounty());
            adminunit.setCity(town.getCounty().getCity());
            adminunit.setProvince(town.getCounty().getCity().getProvince());
            adminunit.setCountry(town.getCounty().getCity().getProvince().getCountry());
        }
        adminunit.setPrefix(prefix);
        adminunit.setAreacode(areacode);
        adminunit.setZipcode(zipcode);
        adminunitRepository.save(adminunit);
        return adminunit;
    }*/

    /**假如Adminunit不存在，提交创建new申请。,但是状态是未确认的。请耐心等待。
     * */
    /*@Transactional
    public Address newPosition(AddressInput par) {
        Assert.isTrue(StringUtils.hasText(par.getName()),"没地址名");
        QAdminunit qm = QAdminunit.adminunit;
        BooleanBuilder builder = new BooleanBuilder();
        Tool.ResolvedGlobalId  glId;
        Adminunit adminunit=null;   //Adminunit是强制前置条件的，必须存在！以最小行政级别为优先的。
        if(StringUtils.hasText(par.getTown())){
            glId= Tool.fromGlobalId(par.getTown());
            builder.and(qm.town.id.eq(Long.parseLong(glId.getId())));
            adminunit=adminunitRepository.findOne(builder).orElse(null);
        }
        else if(StringUtils.hasText(par.getCounty())){
            glId= Tool.fromGlobalId(par.getCounty());
            builder.and(qm.county.id.eq(Long.parseLong(glId.getId())));
            adminunit=adminunitRepository.findOne(builder).orElse(null);
        }
        else if(StringUtils.hasText(par.getCity())){
            glId= Tool.fromGlobalId(par.getCity());
            builder.and(qm.city.id.eq(Long.parseLong(glId.getId())));
            adminunit=adminunitRepository.findOne(builder).orElse(null);
        }
        else if(StringUtils.hasText(par.getProvince())){
            glId= Tool.fromGlobalId(par.getProvince());
            builder.and(qm.province.id.eq(Long.parseLong(glId.getId())));
            adminunit=adminunitRepository.findOne(builder).orElse(null);
        }
        else if(StringUtils.hasText(par.getCountry())){
            glId= Tool.fromGlobalId(par.getCountry());
            builder.and(qm.country.id.eq(Long.parseLong(glId.getId())));
            adminunit=adminunitRepository.findOne(builder).orElse(null);
        }
        Assert.isTrue(adminunit != null,"未找到adminunit请上报管理员申请新增:townId="+par.getTown());
        //adminunit +name 确保唯一性：
        Assert.isTrue(null==addressRepository.findByNameAndAdEquals(par.getName(), adminunit), "地址已存在:"+par.getName());
        Address position = new Address();
        position.setName(par.getName());
        position.setAd(adminunit);
        if(StringUtils.hasText(par.getLat()) && StringUtils.hasText(par.getLon()))
                position.setLatAndLon(par.getLat(), par.getLon());
        if(StringUtils.hasText(par.getVlg())) {
            glId= Tool.fromGlobalId(par.getVlg());
            Village village = villageRepository.findById(Long.parseLong(glId.getId())).orElse(null);
            Assert.isTrue(null!=village, "没找到楼盘:"+par.getVlg());
            position.setVlg(village);
        }
        position.setPlcls(par.getPlcls());
        position.setDense(par.getDense());
        position.setSeimp(par.getSeimp());
        //todo:后台管理确认地址 position.setUsed(true);
        try {
            addressRepository.saveAndFlush(position);
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            Assert.isTrue(false,e.toString());
            return null;
        }
        //todo:需要改成一个个字段赋值！,下一步Json非常巨大，关联信息量大，!影响性能！！！
        String json = JSON.toJSONString(position);   //支持自循环嵌套不报错,按照key/value名字来配对。
        AddressEs objEs=JSON.parseObject(json, AddressEs.class);
        if(null!=position.getLat() && null!=position.getLon() ) {
            GeoPoint geoPoint = new GeoPoint(position.getLat(), position.getLon());
            objEs.setPt(geoPoint);
        }
        if(null!=position.getVlg())
            objEs.setVlgId(position.getVlg().getId());
        objEs.getAd().setCountyId(position.getAd().getCounty().getId());
        objEs.getAd().setCityId(position.getAd().getCity().getId());
        objEs.getAd().setProvinceId(position.getAd().getProvince().getId());
        objEs.getAd().setCountryId(position.getAd().getCountry().getId());
        //相互关联导致的ES存储死循环。ES不支持事务与回滚。
        addressEsRepository.save(objEs);
        //这个地方如果ES抛出异常保存失败，那么mysql数据库也会同样撤销的。
        return position;
    }
    */

    @Transactional
    public Report buildReport(UUID ispId, String no, String path) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Isp isp = iSPRepository.findById(ispId).orElse(null);
        Assert.isTrue(isp != null,"未找到isp:"+isp);
        Report report = new Report(path,isp,no);
        reportRepository.save(report);
        return report;
    }

    @Transactional
    public Task newTask(UUID devsId) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Eqp eQP = eQPRepository.findById(devsId).orElse(null);
        Assert.isTrue(eQP != null,"未找到eQP:"+eQP);
        Task task = new Task();
        List<Eqp> devs=new ArrayList<>();
        devs.add(eQP);
    //todo:    task.setDevs(devs);
        taskRepository.save(task);
        return task;
    }
/**
 * 生成任务
* */
    @Transactional
    public Task buildTask(String dep, LocalDate date, UUID[] eqpIds) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        ParsePosition pos = new ParsePosition(0);
/*        Date date = formatter.parse(sdate, pos);
        if(date == null) {
            throw new BookNotFoundException("日期like非法？", sdate);
        }*/
        Task task = new Task();
        task.setDep(null);
        task.setDate(date);
        //Set<Isp> isps= new HashSet<>();
        List<Isp> isps=new ArrayList<>();
        if(eqpIds!=null){
            for (UUID devId:eqpIds)
            {
                Eqp eQP = eQPRepository.findById(devId).orElse(null);
                if(eQP == null)
                    throw new CommonGraphQLException("未找到eQP:"+devId.toString(),devId.toString());
                Isp isp=new Isp();
                isp.setDev(eQP);
                //todo:  isp.setTask(task);
                isps.add(isp);
                iSPRepository.save(isp);
                //在前端上无法立刻更新，看不见新任务啊；加了底下2行点刷新URL可立刻看见。
                eQP.getIsps().add(isp);
                eQPRepository.save(eQP);
            }
        }
        if(isps.isEmpty() ){    //Task底下竟然都没1个Isp
            Isp isp=new Isp();
            //todo:  isp.setTask(task);
            isps.add(isp);
            iSPRepository.save(isp);
        }
        //todo: task.setIsps(isps);
        taskRepository.save(task);
/*        try {
            Thread.sleep(2000);
            Scanner sc = new Scanner(System.in);
            System.out.println("插桩等待，要不要继续 N/Y:");
            String name = sc.nextLine();
            System.out.println("运行继续您的选择是:[" + name +"]\n");
        }
        catch (Exception e){ }*/
        return task;
    }

    /**当ES保存异常时，重试会影响业务的响应即时性。若ES异常，在RMDB数据库插入一条补救任务，
        有Worker任务会实时地扫这些数据，以RMDB为基准更新ES,通过此补偿机制，来保证ES与RMDB的最终一致性。
    */
    @PreAuthorize("hasRole('Ma')")
    @Transactional(rollbackFor = Exception.class)
    @Deprecated
    public Unit newUnit(String name, String address) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Unit unit = new Unit();
        try {
            units.saveAndFlush(unit);
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return null;
        }
        CompanyEs companyEs =new CompanyEs();
        BeanUtils.copyProperties(unit, companyEs);
        companyEsRepository.save(companyEs);
        return unit;
    }
    //仅用于测试的；
    @Transactional(rollbackFor = Exception.class)
    public Unit newUnitCompany(UnitCommonInput upar, Long id) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Company company=new Company();
        BeanUtils.copyProperties(upar,company);
        Unit unit = new Unit();
        unit.setCompany(company);
        try {
            companies.saveAndFlush(company);
            units.saveAndFlush(unit);
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return null;
        }
        CompanyEs companyEs=new CompanyEs();
        BeanUtils.copyProperties(company,companyEs);
        companyEsRepository.save(companyEs);
        return unit;
    }
    @Transactional(rollbackFor = Exception.class)
    public Unit newUnitPerson(UnitCommonInput upar, Long id) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Person company=new Person();
        BeanUtils.copyProperties(upar,company);
        Unit unit = new Unit();
        unit.setPerson(company);
        try {
            persons.saveAndFlush(company);
            units.saveAndFlush(unit);
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return null;
        }
        PersonEs personEs=new PersonEs();
        BeanUtils.copyProperties(company,personEs);
        personEsRepository.save(personEs);
        return unit;
    }
    @Transactional(rollbackFor = Exception.class)
    public Unit newUnitExternalSource(UnitCommonInput upar) {
        //if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        if(upar.isCompany())
            return   newUnitCompany(upar,0L);
        else
            return   newUnitPerson(upar,0L);
    }
    //无需登录授权访问的特殊函数，graphQL不要返回太多内容如User;
    @MutationMapping
    @Transactional
    public boolean newUser(@Argument String username,@Argument String password,@Argument String mobile,
                           @Argument String external,@Argument String eName,@Argument String ePassword)
    {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        //前置条件验证,OAuth2 外部的系统用户直接授权的情形。
        if(external.equals("旧平台")){
            String err="";
        /* 数据库全无法连 : 旧平台密码不核对也不保存。 暂且改成人工审核身份!
         HrUserinfo hrUser=hrUserinfoRepository.findByUserIdEquals(eName);
            if(hrUser==null)    err="无此用户";
            else if(!hrUser.getPassword().equals(ePassword))    err="密码不对";
            else if(hrUser.getStatus()!=2)    err="非正常用户";
            if(!err.equals(""))  throw new BookNotFoundException("旧平台验证失败"+err,14L); */
        }
        else return false;
        User olduser=userRepository.findByUsername(username);
        Assert.isTrue(null==olduser,"重复的账户名字:"+username);
        //if(null!=olduser)   throw new MsgRuntimeException("重复的账户名字!");
        User user = new User(username);
        String dbmima=passwordEncoder.encode(password);
        user.setPassword(dbmima);
        user.setMobile(mobile);
        user.setAuthName(eName);
        user.setAuthType(external);
        user.set旧账户(eName);
        userRepository.save(user);
        return true;    //都是成功，数据库保存不成功？ 底层就报错;
    }

    @MutationMapping
    @Transactional
    public boolean logout() {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Authentication auth= SecurityContextHolder.getContext().getAuthentication();
        if(auth==null)  return false;
        //"注销时，POST没有附带上 Bearer " 的，没有token,到底是谁啊,需要从OPTIONS才有知晓；或Cookie: token=；
        Object principal=auth.getPrincipal();
        if(principal instanceof JwtUser) {
            UUID userid = ((JwtUser) principal).getId();
            User user = userRepository.findById(userid).orElse(null);
            UserDetails userDetails = jwtUserDetailsService.loadUserByUsername( user.getUsername() );
            log.info("user logout '{}', 注销退出了", user.getUsername());
            userRepository.save(user);
        }
        //没有登录的也Authenticated! 有anonymousUser 有ROLE_ANONYMOUS；
        SecurityContextHolder.getContext().setAuthentication(null);
        HttpServletResponse response=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        Cookie cookie =new Cookie("token", "");
       // cookie.setDomain(cookieDomain);
        cookie.setHttpOnly(true);
        cookie.setMaxAge(1);
        cookie.setPath("/");      //原来是"/" 一个IP只能适配一个token,多个服务端共享IP无法区分各自独立的token=啊？
        cookie.setSecure(isSSLenabled);
        response.addCookie(cookie);
        return true;
    }
    /**用户登录：
     * Cookie对于WebMVC外其他传输层技术不一定适用。封装抽象多层：不一定必是httpServlet的底层 ; Principal principal,
     * SSO: OpenID Connect等，该是这里接收CAS认证服务器的token保存，接口参数应该有外部:refresh_token以及哪一个认证服务者标记:server_name;
     * 还要将refresh_token发认证服务器验明正身{等待server_name应答}，随后的流程保持不变，安全的模式密码照样需要匹配{不安全可免密码}，本服务器照样签发自己token给客户端。
     * 若遵从OAuth 2.1的，外部服务器验证token后，同时也就验证本后端的应该给该客户端的Role授权列表，{不安全模式可采信外部注入的角色再配置机制}，安全模式就流程不变。
     * SSO注入的Role角色不应该保存到本后端数据库上。权限认证应该以内存中authentication{..}的为准。
     */
    @MutationMapping
    @Transactional
    public boolean authenticate(@Argument("username") String username, @Argument("password") String password,
                                GraphQLContext ctx, DataFetchingEnvironment env
    ) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Assert.notNull(username,"username?");
        //ContextView contextView=ctx.get("org.springframework.graphql.execution.ReactorContextManager.CONTEXT_VIEW");
        //LinkedHashMap accessor=contextView.get("org.springframework.graphql.execution.ReactorContextManager.THREAD_VALUES_ACCESSOR");
        //Object  reqac= accessor.get("org.fjsei.yewu.greeting.RequestAttributesAccessor");
        User user = userRepository.findByUsername(username);         //username is Unique, but not user.id!
        Assert.isTrue(user != null,"没用户:"+username);
        //经验证BCryptPasswordEncoder只能支持最大72个字符密码长度，后面超过部分被直接切除掉了。
        boolean isMatch= passwordEncoder.matches(password,user.getPassword());
        Assert.isTrue(isMatch,"密码错:"+username);
        log.debug("checking authentication for user '{}'", username);
        UserDetails userDetails = jwtUserDetailsService.loadUserByUsername( user.getUsername());
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        ServletRequestAttributes servletRequestAttributes= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
        HttpServletRequest request =servletRequestAttributes.getRequest();
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));      //?context 关联UserDetails
        log.info("authorizated user '{}', setting security context", username);
        SecurityContextHolder.getContext().setAuthentication(authentication);
//        String token = jwtTokenUtil.generateToken(userDetails);     //jwtTokenUtil.validateToken(authToken, userDetails)
        TokenDto jwt = jwtTokenProvider.createToken(authentication);
        //浏览器自动遵守标准：超时的cookie就不会该送过来了。 那万一不守规矩？两手准备。
        HttpServletResponse response=servletRequestAttributes.getResponse();     //servletContext.getHttpServletResponse();
        Cookie cookie =new Cookie("token", jwt.getAccessToken());
    //Domain是针对后端服务器，前端跨域，浏览器处理时和前端是那个的一点关系都没有。  无法识别端口号的，只能识别IP域名;
        //若undertow不能加域名IP, 而tomcat可以加IP的。
        //cookie.setDomain(cookieDomain);    这个参数必须以“.”开始。
        cookie.setHttpOnly(true);
        cookie.setMaxAge(accessTokenValidityInSeconds);      //这个时间和token内部声称的时间不同，这给浏览器用的 = 1.5个小时。
        //Path就是servlet URL的接口路径，不能嵌套在其它servlet的底下; 目前只能针对某个IP地址，不识别http端口号的。所以添加path加以区分不同的后端服务器。
        cookie.setPath("/");       //cookie.setPath("/graphql")限定只能是graphql其他API入口URI的请求包就没有附带token
        cookie.setSecure(isSSLenabled);     //只有HTTPS SSL才允許設。
        response.addCookie(cookie);
        Authentication auth= SecurityContextHolder.getContext().getAuthentication();
        UUID  userid= ((JwtUser)(auth.getPrincipal())).getId();
        //返回权限列表 【注意】JWT token不区分端口号;
        return true;
    }

/*    @Transactional
    public Equipment setEQPPosUnit(Long id, Long posId, Long ownerId, Long maintId) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Eqp eQP = eQPRepository.findById(id).orElse(null);
        Assert.isTrue(eQP != null,"未找到eQP:"+eQP);
        Address position= addressRepository.findById(posId).orElse(null);
        Unit ownerUnit= unitRepository.findById(ownerId).orElse(null);
        Unit maintUnit= unitRepository.findById(maintId).orElse(null);
        Assert.isTrue(position != null,"未找到position:"+position);
        Assert.isTrue(ownerUnit != null,"未找到ownerUnit:"+ownerUnit);
        eQP.setPos(position);
        eQP.setOwner(ownerUnit);
        eQP.setMtu(maintUnit);
        eQPRepository.save(eQP);
        return (Equipment) eQP;
    }*/


    @Transactional
    public PipingUnit buildPipingUnit(String id, PipingUnitInput in) {
        Tool.ResolvedGuuid gId= Tool.fromGluuId(id);
        PipingUnit pipingUnit = pipingUnitRepository.findById(gId.getId()).orElse(null);
        Assert.isTrue(pipingUnit != null,"未找到PipingUnit:"+id);
        Unit unitDesu=null, unitInsu=null;
        if(StringUtils.hasText(in.getDesu())) {
            gId = Tool.fromGluuId(in.getDesu());
            if(gId.getType().equals("CompanyEs") )
                unitDesu= units.findUnitByCompany_Id(gId.getId());
            else if(gId.getType().equals("PersonEs") )
                unitDesu= units.findUnitByPerson_Id(gId.getId());
            else
                unitDesu= units.findById(gId.getId()).orElse(null);
           Assert.isTrue(unitDesu != null,"未找到Unit:"+in.getDesu());
        }
        if(StringUtils.hasText(in.getInsu())) {
            gId = Tool.fromGluuId(in.getInsu());
            if(gId.getType().equals("CompanyEs") )
                unitInsu= units.findUnitByCompany_Id(gId.getId());
            else if(gId.getType().equals("PersonEs") )
                unitInsu= units.findUnitByPerson_Id(gId.getId());
            else
                unitInsu= units.findById(gId.getId()).orElse(null);
            Assert.isTrue(unitInsu != null,"未找到Unit:"+in.getInsu());
        }
        Adminunit adminunit=null;
        if(StringUtils.hasText(in.getAd())) {
            Tool.ResolvedGuuid   glId= Tool.fromGluuId(in.getAd());
            adminunit = adminunitRepository.findById(glId.getId()).orElse(null);
            Assert.isTrue(adminunit != null, "未找到Adminunit:" + in.getAd());
        }
        pipingUnit= ((PipingUnit) pipingUnit).toBuilder().name(in.getName()).proj(in.getProj()).start(in.getStart()).stop(in.getStop())
                .regd(in.getRegd()).dia(in.getDia()).thik(in.getThik()).leng(in.getLeng()).level(in.getLevel())
                .lay(in.getLay()).matr(in.getMatr()).mdi(in.getMdi()).nxtd1(in.getNxtd1()).nxtd2(in.getNxtd2()).svp(in.getSvp()).pa(in.getPa())
                .used(in.getUsed()).insd(in.getInsd()).desu(unitDesu).insu(unitInsu).safe(in.getSafe()).ust(in.getUst())
                .ad(adminunit).build();
        pipingUnitRepository.save(pipingUnit);
        return pipingUnit;
    }
    @Transactional
    public Equipment buildEQP2(UUID id, Long ownerId, DeviceCommonInput info) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Eqp eQP = eQPRepository.findById(id).orElse(null);
        Assert.isTrue(eQP != null,"未找到eQP:"+eQP);

        eQPRepository.save(eQP);
        return (Equipment) eQP;
    }
    @Transactional
    public Elevator buildElevator_222(UUID id, UUID ownerId, DeviceCommonInput info) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Eqp eQP = eQPRepository.findById(id).orElse(null);
        Assert.isTrue(eQP == null,"找到eQP:"+eQP);
        Elevator elevator =Elevator.builder().cod(info.getCod()).type("typ01").oid(info.getOid()).build();
        //elevator.setLiftHeight("565555");
        //elevatorRepository.save(elevator);
        return elevator;
    }
    @Transactional
    public Elevator buildElevator(String cod, String type, String oid) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Elevator elevator =Elevator.builder().cod(cod).type(type).oid(oid).build();
        //elevator.setLiftHeight("565555");
        //elevatorRepository.save(elevator);
        return elevator;
    }
    /**纯粹为了不报错！ Boiler在graphQL定义，但是没地方用到
    Object type 'Boiler' implements a known interface, but no class could be found for that type name.  Please pass a class for type 'Boiler' in the parser's dictionary.
   */
    public Boiler buildBoilerFake(String oid) {
        return (Boiler)null;
    }
    public Crane buildCraneFake(String oid) {  return (Crane)null;  }
    public Vessel buildVesselFake(String oid) { return (Vessel)null; }
    public FactoryVehicle buildFactoryVehicleFake(String oid) { return (FactoryVehicle)null; }
    public Amusement buildAmusementFake(String oid) { return (Amusement)null; }
    public Pipeline buildPipelineFake(String oid) { return (Pipeline)null; }



    @Transactional
    public Equipment testEQPModify(UUID id, String oid) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        String  prevOid="is null";
        Map<String, Object>  properties1=emSei.getProperties();
        emSei.setProperty(JPA_SHARED_CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS);
        emSei.setProperty(JPA_SHARED_CACHE_STORE_MODE, CacheStoreMode.REFRESH);
        Eqp eQP =eQPRepository.findById(id).orElse(null);
        if(eQP==null)   return  eQP;
        prevOid=eQP.getOid();
        eQP.setOid(oid);
        eQPRepository.save(eQP);
        return  (Equipment) eQP;
    }
    @Transactional
    public String testEQPFindModify(String cod,String oid) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        String  prevOid="is null";
        Map<String, Object>  properties1=emSei.getProperties();
        Eqp eQP = eQPRepository.findByCod(cod);
        if(eQP==null)   return  prevOid;
        prevOid=eQP.getOid();
        eQP.setOid(oid);
        eQPRepository.save(eQP);
        return  prevOid;
    }
    @Transactional
    public String testEQPStreamModify222(String cod,String oid) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        String  prevOid="not find,is null?";
        emSei.setProperty(JPA_SHARED_CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS);
        emSei.setProperty(JPA_SHARED_CACHE_STORE_MODE, CacheStoreMode.REFRESH);
        List<Eqp> eqpList= eQPRepository.findAll();     //较慢:所有数据都装载了
        List<Eqp> eqpObjs=eqpList.stream().filter(e -> e.getCod().equals(cod)).collect(Collectors.toList());
        for (Eqp eQP:eqpObjs)
        {
            prevOid = eQP.getOid();
            eQP.setOid(oid);
            eQPRepository.save(eQP);
        }
        if(eqpObjs.size()>1)    prevOid="超过1个的eqp?";
        return  prevOid;
    }
    @Transactional
    public String testEQPStreamModify(String cod,String oid) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        String  prevOid="not find,is null?";
        //必须加这2行，否则可能无法获取最新DB数据，可能不被认定为必须做DB更新。
        emSei.setProperty(JPA_SHARED_CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS);
        emSei.setProperty(JPA_SHARED_CACHE_STORE_MODE, CacheStoreMode.REFRESH);
        //虽然findAll()被注解@QueryHints(HINT_CACHEABLE,true)了！　可是这里是不会从缓存读的，都会直接查数据库。
        List<Eqp> eqpList= eQPRepository.findAll();     //较慢:所有数据都装载了
        List<Eqp> eqpObjs=eqpList.stream().filter(e -> e.getCod().equals(cod)).collect(Collectors.toList());
        for (Eqp eQP:eqpObjs)
        {
            prevOid = eQP.getOid();
     //       if(eQP instanceof Elevator)
     //           ((Elevator) eQP).setLiftHeight("ss231");
            eQPRepository.save(eQP);
        }
        if(eqpObjs.size()>1)    prevOid="超过1个的eqp?";
        return  prevOid;
    }
    @Transactional
    public Equipment testEQPCriteriaModify(String cod, String oid, String type) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        //必须加这2行，否则可能无法获取最新DB数据，可能不被认定为必须做DB更新。
        emSei.setProperty(JPA_SHARED_CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS);
        emSei.setProperty(JPA_SHARED_CACHE_STORE_MODE, CacheStoreMode.REFRESH);
        String  prevOid="not find,is null?";
        Pageable pageable = PageRequest.of(0, 50, Sort.by(Sort.Direction.ASC,"oid"));         //Integer.parseInt(10)
        Page<Eqp> allPage=eQPRepository.findAll(new Specification<Eqp>() {
            @Override
            public Predicate toPredicate(Root<Eqp> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicateList = new ArrayList<Predicate>();
                if (StringUtils.hasLength(cod)) {
                    Path<String> p = root.get("cod");
                    predicateList.add(cb.like(p,"%" + cod + "%"));
                }
                if (StringUtils.hasLength(type)) {
                    Path<String> p = root.get("type");
                    predicateList.add(cb.equal(p,type));
                }
                Predicate[] predicates = new Predicate[predicateList.size()];
                predicateList.toArray(predicates);
                query.where(predicates);
                return null;
            }
        }, pageable);
        List<Eqp>  eqpObjs= allPage.getContent();
        for (Eqp eQP:eqpObjs)
        {
            prevOid = eQP.getOid();
            eQP.setOid(oid);
            eQPRepository.save(eQP);     //若缓存数据没有变换，这个不一定会提交给数据库？等于没干活。
        }
        if(eqpObjs.size()>1)    prevOid="超过1个的eqp?";
        return (Equipment) eqpObjs.get(0);
    }
    @Transactional
    public String testEQPcreateQueryModify(String cod,String oid) {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        String  prevOid="is null";
        Map<String, Object>  properties1=emSei.getProperties();
        List<Eqp> eQPs = emSei.createQuery(
                "select DISTINCT e from EQP e where id=2119", Eqp.class)
                .getResultList();
        Eqp eQP= eQPs.get(0);
        if(eQP==null)   return  prevOid;
        prevOid=eQP.getOid();
        eQP.setOid(oid);
        eQPRepository.save(eQP);
        Map<String, Object>  properties2=emSei.getProperties();
        return  prevOid;
    }
    @MutationMapping
    @Transactional
    public boolean deleteReport(@Argument String repId,@Argument String reason)
    {
        Tool.ResolvedGuuid globalId = Tool.fromGluuId(repId);
        Report report = reportRepository.findById(globalId.getId()).orElse(null);
        Assert.isTrue(report != null,"未找到Report:"+repId);
        Assert.isTrue(report.getFiles().isEmpty(),"还有File关联"+repId);
        if(report.equals(report.getIsp().getReport()) )
            report.getIsp().setReport(null);
        emSei.remove(report);
        emSei.flush();
        return report!=null;
    }

    @Transactional
    public boolean invalidateEQP(UUID eqpId,String reason)
    {
        if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
        Eqp eqp = eQPRepository.findById(eqpId).orElse(null);
        Assert.isTrue(eqp != null,"未找到EQP:"+eqpId);
        eqp.setUst(UseState_Enum.STOP);
        eQPRepository.save(eqp);
        return eqp!=null;
    }
    @Transactional
    public boolean removeEQP(String eqpId)
    {
        Tool.ResolvedGuuid gId= Tool.fromGluuId(eqpId);
        UUID id= gId.getId();
        Eqp eqp = eQPRepository.findById(id).orElse(null);
        //Assert.isTrue(eqp != null,"未找到EQP:"+eqpId);
        if(null!=eqp)
            eQPRepository.delete(eqp);
        boolean hasEqp2 = eqpEsRepository.existsById(id);
        if(hasEqp2)   eqpEsRepository.deleteById(id);
        //eQPRepository.flush();
        return eqp!=null;
    }
    @MutationMapping
    @Transactional
    public boolean removePipingUnit(@Argument("id") String puId)
    {
        Tool.ResolvedGuuid gId= Tool.fromGluuId(puId);
        PipingUnit pipingUnit = pipingUnitRepository.findById(gId.getId()).orElse(null);
        if(null!=pipingUnit)
            pipingUnitRepository.delete(pipingUnit);
        return pipingUnit!=null;
    }

}



/*
加了cache缓存后，为了在事务中读取数据库最新数据：emSei.find(Eqp.class,id)或eQPRepository.findById(id)或eQPRepository.getOne(id)或findAll()；
                或eQPRepository.findAll(new Specification<Eqp>() {@Override },pageable);
必须加  emSei.setProperty(JPA_SHARED_CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS);
        emSei.setProperty(JPA_SHARED_CACHE_STORE_MODE, CacheStoreMode.REFRESH); 加了这2条才能从DB去取最新数据。
而这些方法无需添加也能去数据库取最新数据：eQPRepository.findByCod(cod)或emSei.createQuery("")
[淘汰做法] 利用GraphQLMutationResolver的接口映射函数参数只认参数个数和顺序，不认参数名字和参数类型的代理机制，直接把参数ID类型映射到某个工具类Mnode的，
随后在接口函数类依据传入的函数Mnode参数字段进一步提取ID对应的实体Entity实例;该做法行的通但是意义不大且看着怪异。
 try {  服务器被挂起的 服务端输入测试：
            Thread.sleep(2000);
            Scanner sc = new Scanner(System.in);
            System.out.println("插桩等待，要不要继续 N/Y:");
            String name = sc.nextLine();
            System.out.println("运行继续您的选择是:[" + name +"]\n");
        }
        catch (Exception e){ }
*/

